#pragma once

#include <ctime>
#include <iostream>
#include <string>
#include <vector>

class Memento
{
public:
    virtual ~Memento(){};

public:
    virtual std::string getName() const = 0;
    virtual std::string date() const    = 0;
    virtual std::string state() const   = 0;
};

class ConcreteMemento : public Memento
{
public:
    ConcreteMemento(std::string state) : m_state(state)
    {
        this->m_state   = state;
        std::time_t now = std::time(0);
        this->m_date    = std::ctime(&now);
    }

public:
    std::string getName() const override
    {
        return "[" + this->m_date.substr(0, m_date.length() - 1) + "] / (" + this->m_state.substr(0, 9) + "...)";
    }

    std::string date() const override
    {
        return this->m_date;
    }

    std::string state() const override
    {
        return this->m_state;
    }

private:
    std::string m_state;
    std::string m_date;
};

class Originator
{
public:
    Originator(std::string state) : m_state(state)
    {
        std::cout << "Originator: My initial state is: " << this->m_state << std::endl;
    }

public:
    void doSomething()
    {
        this->m_state = this->generateRandomString(30);
        std::cout << "Originator: I'm doing something, my state has changed to: " << this->m_state << std::endl;
    }

    Memento *save()
    {
        return new ConcreteMemento(this->m_state);
    }

    void restore(Memento *memento)
    {
        this->m_state = memento->state();
    }

private:
    std::string generateRandomString(int length = 10)
    {
        const char  alphanum[]   = "0123456789"
                                   "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                   "abcdefghijklmnopqrstuvwxyz";
        int         stringLength = sizeof(alphanum) - 1;
        std::string randomString;
        for (int i = 0; i < length; i++) {
            randomString += alphanum[std::rand() % stringLength];
        }
        return randomString;
    }

private:
    std::string m_state;
};

class Caretaker
{
public:
    Caretaker(Originator *originator) : m_originator(originator){};
    ~Caretaker()
    {
        for (auto memento : m_mementos) {
            delete memento;
        }
    }

public:
    void backup()
    {
        std::cout << "Caretaker: Saving Originator's state..." << std::endl;
        this->m_mementos.push_back(this->m_originator->save());
    }

    void undo()
    {
        if (!this->m_mementos.size()) {
            return;
        }

        Memento *memento = this->m_mementos.back();
        this->m_mementos.pop_back();
        std::cout << "Caretaker: Restoring state to: " << memento->getName() << std::endl;

        try {
            this->m_originator->restore(memento);
        } catch (...) {
            this->undo();
        }
    }

    void showHistory()
    {
        std::cout << "Caretaker: Here's the list of mementos:" << std::endl;
        for (Memento *memento : this->m_mementos) {
            std::cout << memento->getName() << std::endl;
        }
    }

private:
    std::vector<Memento *> m_mementos;
    Originator            *m_originator;
};
